package com.yj.springbootmiaosha.zkLock;

import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

/**
 * 使用 zookeeper 实现分布式锁
 */
@Slf4j
public class ZkLock implements Watcher, AutoCloseable {

    private ZooKeeper zooKeeper;
    private String connectionString = "192.168.152.12:2181";
    private String znode;


    public ZkLock() throws IOException {
        this.zooKeeper = new ZooKeeper(connectionString, 40000, this);
    }

    public boolean getLock(String key) {
        try {
            // 1、先判断业务父节点存不存在
            Stat stat = zooKeeper.exists("/" + key, false);
            if (null == stat) {
                // 不存在，则创建业务父节点
                // 创建一个持久的父节点
                zooKeeper.create("/" + key, key.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
            }

            // 2、创建临时有序节点， /order/order_00000001
            znode = zooKeeper.create("/" + key + "/" + key + "_", key.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

            // 3、获取父节点下的所有临时有序节点
            List<String> nodes = zooKeeper.getChildren("/" + key, false);

            // 4、按照大小排序
            Collections.sort(nodes);

            // 5、如果第一个节点 和 传入的节点 相同就 获取到锁
            String firstNode = nodes.get(0);
            if (znode.endsWith(firstNode)) {
                return true;
            }

            // 6、如果传入的节点不是第一个节点，则需要监听他的前一个节点
            String lastNode = firstNode;
            for (String node : nodes) {
                if (znode.endsWith(node)) {
                    zooKeeper.exists("/" + key + "/" + lastNode, true);
                    break;
                } else {
                    lastNode = node;
                }
            }
            // 7、阻塞当前线程需要等按照节点顺序获取到锁
            // 如果当前节点的前一个节点释放了锁，被删除了，才唤醒
            synchronized (this) {
                wait();
            }
            // 8、唤醒之后获取倒锁，则返回true
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 监听器
     *
     * @param watchedEvent
     */
    @Override
    public void process(WatchedEvent watchedEvent) {
        //如果前一个临时顺序节点被删除了，则唤醒当前持有锁的节点
        if (watchedEvent.getType().equals(Event.EventType.NodeDeleted)) {
            synchronized (this) {
                notify();
            }
        }
    }

    @Override
    public void close() throws Exception {
        zooKeeper.delete(znode, -1);
        zooKeeper.close();
        log.info("已经释放了锁");
    }
}
